﻿//#define MyDebug

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
//using System.Windows.Forms;
using Microsoft.DirectX;
using Microsoft.DirectX.Direct3D;
using System.Drawing; //For Color(s) class
using System.Diagnostics;
using DirectX_CS;
using System.Runtime.InteropServices;// for System.Runtime.InteropServices.MarshalAs

namespace DirectX_CS
{
	class Terrain 
	{

	#region "Member data and Constructor"

		//we use lists of data rather than arrays...
		//private Vector3[] mDataVertices = null; 		//we use lists of data rather than arrays...
		private Vector3[] mDataVertices = null; 		//we use lists of data rather than arrays...
		private int[] mAdjacencyData;
		//THE terrain data vertices list has a corresponding list called the index list.
		//Which defines a function onto a list of vertices.
		private short[] mTerrainIndices = null; 		//we use lists of data rather than arrays...
		private Device mDevice = null;
		
		private Mesh mMesh = null; //requires instance of Device, see constructor for its instantation and for constructor.

		#if MyDebug
		private TraceListener debugL = null;
		#endif 

		//public unsafe void ConstructUnsafeStreamsAndDraw()
		//{
		//    fixed ( void* verticesPointer = mDataVertices)
		//    fixed (void* indicesPointer = mTerrainIndices)
		//    {
		//        GraphicsStream indicesStream = new GraphicsStream(indicesPointer, sizeof(int) * mTerrainIndices.Length, mRead, mWrite);

		//        int size = System.Runtime.InteropServices.Marshal.SizeOf(mDataVertices);
		//        int size = sizeof(Vector3) * mDataVertices.Length;
		//        GraphicsStream verticesStream = new GraphicsStream(verticesPointer, size, mRead, mWrite);

		//        mDevice.DrawIndexedPrimitives(PrimitiveType.TriangleList, 0, 0, VertexCount, 0, 1);
		//        mDevice.DrawIndexedPrimitives(PrimitiveType.LineList, 0, 0, VertexCount, 0, 1);
		//        mDevice.DrawPrimitives(PrimitiveType.LineList, 0, VertexCount);

		//    }
		//}

		public Terrain()
		{
			ConstructGeneral();
		}
		public Terrain(Device dev)
		{
			mDevice = dev;
			ConstructGeneral();
		}
		public void ConstructGeneral()
		{
			#if MyDebug
			debugL = new TextWriterTraceListener(System.Console.Out, "debug");
			System.Diagnostics.Debug.Listeners.Add(debugL);
			#endif
			Tesselation = DefaultEqualDivisions; //change to whatever... 
		}
	#endregion "Member data and Constructor"
		

		public void LoadData()
		{
			#if MyDebug
			DebugShowDataIndexMap();
			#endif

			//Create Mesh Data
			CreateHeightData();
			//CreateTerrainData();
			//CreateMeshFromHeightData();
			//CreatePremadeMesh();

			CreateSomething();
			
		}

		private void CreateSomething()
		{
			int numVertices = 2;
			int primitivesToDraw = 1;
			//mDevice.DrawPrimitives(PrimitiveType.LineStrip, 0, primitivesToDraw);
			//mDevice.DrawPrimitives(PrimitiveType.TriangleStrip, 0, primitivesToDraw);

			int baseVertexIdx = 0;
			mDevice.DrawIndexedPrimitives(PrimitiveType.LineStrip, baseVertexIdx, baseVertexIdx, numVertices, baseVertexIdx, primitivesToDraw);

		}

		public void DrawStuff()
		{
			CreateSomething();
		}

		public Device Device
		{
			get { return mDevice; }
			set { mDevice = value; }
		}
		/*
		private Vector3[] TerrainVertices()
		{
			return mDataVertices;
		}

		private int[] TerrainIndices()
		{
			return mTerrainIndices;
		}*/

		private void CreateMeshFromHeightData()
		{
			//mMesh = new Mesh(FaceCount, this.VertexCount, MeshFlags.Managed, 
			//	VertexFormats.Position, mDevice);

			// cannot do: GraphicsStream declaredGraphicsStream = new GraphicsStream(; //because GraphicsStream cannot be created safely.?
			//mMesh = new Mesh(FaceCount, this.mDataVertices.Length, MeshFlags.SoftwareProcessing, 
			//	VertexFormats.Position,  mDevice);

			/*
			//int streamOrIdx = 0; int offset = 0;
			const short FIRST_STREAM = 0;
			VertexElement ve = new VertexElement();
			ve.DeclarationMethod = DeclarationMethod.Default;
			ve.DeclarationType = DeclarationType.Float3;
			ve.DeclarationUsage = DeclarationUsage.Position;
			ve.Stream = FIRST_STREAM;
			//new VertexElement(streamOrIdx, offset, DeclarationType.Float3, DeclarationMethod.Default, DeclarationUsage.Position, 0);
			//(short stream, short offset, DeclarationType declType, DeclarationMethod declMethod, DeclarationUsage declUsage, byte usageIndex);
			VertexElement[] vea = { ve };
			 * 
			 * 

			VertexElement[] vea = new VertexElement[] { new VertexElement() };
			vea[0].DeclarationMethod = DeclarationMethod.Default;
			vea[0].DeclarationType = DeclarationType.Float3;
			vea[0].DeclarationUsage = DeclarationUsage.Position;
			*/

			/* //Worked but didnt render
			mMesh = new Mesh(FaceCount, VertexCount, 
				MeshFlags.IbManaged | MeshFlags.VbManaged | MeshFlags.Points , 
				VertexFormats.Position, mDevice); //MeshFlags.SimplifyVertex 
			mMesh.SetVertexBufferData(mDataVertices, LockFlags.ReadOnly );

			*/
			mMesh = Mesh.Cylinder(mDevice, 1, 1, 1, 100,100);
			VertexBuffer vb = mMesh.VertexBuffer; //no legible data

			//http://www.devmaster.net/forums/showthread.php?t=5956
			mAdjacencyData = new int[mMesh.NumberFaces * 3];
			mMesh.GenerateAdjacency(.5f, mAdjacencyData);
			//mMesh.Optimize(MeshFlags.OptimizeCompact, mAdjacencyData);
			// this will still generate an attribute table (on its own, though)
			//mMesh.Optimize( MeshFlags.Points | MeshFlags.VbManaged | MeshFlags.SimplifyVertex, 
			//	mAdjacencyData);

			//mMesh.OptimizeInPlace(MeshFlags.Dynamic | MeshFlags.Points  , //MeshFlags.SimplifyVertex 
			//	mAdjacencyData);//adjacency);
			// MeshFlags.OptimizeVertexCache 

			//mMesh.SetIndexBufferData(mAdjacencyData, LockFlags.None);

			AttributeRange rangeAll = new AttributeRange();
			rangeAll.AttributeId = 0;
			rangeAll.VertexCount = VertexCount;
			rangeAll.VertexStart = 0;
			rangeAll.FaceStart = 0;
			rangeAll.FaceCount = FaceCount;
			mMesh.SetAttributeTable(new AttributeRange[] { rangeAll });

		}

		private void CreatePremadeMesh()
		{
			mMesh = Mesh.Cylinder(mDevice, 1f, 1f, 1f, 20, 20);

			AttributeRange rangeAll = new AttributeRange();
			rangeAll.AttributeId = 0;
			rangeAll.VertexCount = mMesh.NumberVertices;
			rangeAll.VertexStart = 0;
			rangeAll.FaceStart = 0;
			rangeAll.FaceCount = mMesh.NumberFaces;
			mMesh.SetAttributeTable(new AttributeRange[] { rangeAll });
			
		}

		#region "Old code related to doit"
		/*
			//VertexBuffer vb = new VertexBuffer(typeof(Vector3), mDataVertices.Length, mDevice, Usage.Points, VertexFormats.Position | VertexFormats.Normal, Pool.Managed);
			//vb.SetData(mDataVertices, 0, LockFlags.None);

			//IndexBuffer ib = new IndexBuffer(typeof(int), mTerrainIndices.Length, mDevice, Usage.Points, Pool.Managed);
			//ib.SetData(mTerrainIndices, 0, LockFlags.None);
			//mMesh.SetIndexBufferData(ib, LockFlags.None);
			//mMesh.SetVertexBufferData(vb, LockFlags.None);

			//int bufSize = mTerrainIndices.Length * sizeof(System.Int16);
			VertexElement[] va = new VertexElement[]
				{
					new VertexElement(0, 0, DeclarationType.Float3, DeclarationMethod.Default, DeclarationUsage.Position, 0),
					new VertexElement(0, 12, DeclarationType.Float3, DeclarationMethod.Default, DeclarationUsage.Normal, 0),
					//new VertexElement(0, 0, DeclarationType.Float1, DeclarationMethod.Default, DeclarationUsage.Color, 0),
					VertexElement.VertexDeclarationEnd
			//	};
			//VertexDeclaration vd = new VertexDeclaration(mDevice, va);
			//mDevice.VertexDeclaration = vd;
			//mDevice.VertexFormat = VertexFormats.Position | VertexFormats.Normal;

			//int vertexBytes = sizeof(float) * 6;
			//System.Diagnostics.Debug.WriteLine("VertexBytes = " + vertexBytes);
*/
		#endregion

		public void DrawPrimitivesOld()
		{
			//mDevice.SetStreamSource(0, mDataVertices[0], sizeof(Vector3));
			//mDevice.Indices = mTerrainIndices[0];

			Vector3 sample = new Vector3();
			int structSize = System.Runtime.InteropServices.Marshal.SizeOf(sample);
			int arraySize = structSize * VertexCount;
			
			VertexBuffer vb = new VertexBuffer(mDevice, 
				arraySize, 
				Usage.Points, VertexFormats.Position, Pool.Managed);
			
			vb.Lock(0, 0, LockFlags.NoOverwrite);
			vb.SetData(mDataVertices, 0, LockFlags.None);
			vb.Unlock();
			

			/*
			int sizeOfBuffer = sizeof(short) * VertexCount;
			bool useShort = mTerrainIndices[0].GetType() == typeof(short);
			IndexBuffer ib = new IndexBuffer(mDevice, sizeOfBuffer, Usage.None, Pool.Managed, useShort);
			//ib.Lock(0, 0, LockFlags.NoOverwrite);
			ib.SetData(mTerrainIndices, 0, LockFlags.None);
			mDevice.Indices = ib;
			ib.Unlock();

			mDevice.DrawIndexedPrimitives(PrimitiveType.TriangleStrip, 0, 0, VertexCount, 0, primCount);

			 * */
			//int primCount = 1;

			//we only have one buffer so 
			//mDevice.SetStreamSource(0, vb, 0);
			mDevice.SetStreamSource(0, vb,  structSize);

			int oOffset = 0;
			int oStride = 0; //see if it changes these.
			mDevice.GetStreamSource(0, out oOffset, out oStride);

			//http://msdn.microsoft.com/en-us/library/microsoft.xna.framework.graphics.primitivetype.aspx
			//mDevice.DrawUserPrimitives(PrimitiveType.LineList, VertexCount/2, mDataVertices);
			//mDevice.DrawPrimitives(PrimitiveType.PointList, 0, 10);
		}

		public void DrawMesh()
		{
			AttributeRange[] ar = mMesh.GetAttributeTable();
			int numSubSets = ar.Length;

			for (int i = 0; i < numSubSets; ++i)
			{
				mMesh.DrawSubset(i);
			}
		}

	#region "Fundamental properties"

		float xMin=0, xMax;
		public float XMax
		{
			get { return xMax; }
			set { xMax = value; }
		}

		public float XMin
		{
			get { return xMin; }
			set { xMin = value; }
		}
		float yMin=0, yMax;
		public float YMax
		{
			get { return yMax; }
			set { yMax = value; }
		}
		public float YMin
		{
			get { return yMin; }
			set { yMin = value; }
		}
		//float ZMin = -1, ZMax = 1;
		public const short DefaultEqualDivisions = 4;

		public short Tesselation
		{
			set
			{
				if (value == 0 )
					throw new Exception("Tesselation must be a nonzero number.");
				XDivisions = value;
				YDivisions = value;
				//ZDivisions = value;
			}
		}

		short xDivisions = DefaultEqualDivisions;
		public short XDivisions
		{
			get { return xDivisions; }
			set { xDivisions = value;
				  XMax = (float)xDivisions;
				}
		}

		short yDivisions = DefaultEqualDivisions; //or just set them seperately
		public short YDivisions
		{
			get { return yDivisions; }
			set { yDivisions = value;
				  YMax = (float)yDivisions;
			}
		}

		public int DataCount
		{
			get { return (EvalsX) * (EvalsY); }
		}

		public int NumberOfFaces
		{
			get { return xDivisions * yDivisions; }
		}

		public int EvalsX
		{
			get { return xDivisions + 1; }
		}

		public int EvalsY
		{
			get { return yDivisions + 1; }
		}

		public int Evals()
		{
			return EvalsX * EvalsY;
		}

		float DX
		{
			get { return (XMax - XMin) / xDivisions; }
		}
		float DY
		{
			get { return (YMax - YMin) / yDivisions; }
		}

		//public int Area
		//{
		///	get { return xDivisions * yDivisions; }
		//}
#endregion


	#region "Create data / compute data/ calculations "
		/// <summary>
		/// Create the height data vertex by vertex.
		/// </summary>
		public void CreateHeightData()
		{
			mDataVertices = new Vector3[EvalsX * EvalsY];

			//for vertex types with texture coordinates member.
			//float u = 0f;
			//float v = 0f;

			int index = 0;
			float y = YMin;
			float x = XMin;
			while (index < mDataVertices.Length)
			{
				//now save the generated point
				float z = F_z(x, y);
				
				Vector3 positionV = new Vector3(x, y, z);
				mDataVertices[index] = positionV;

				#if MyDebug
				string msg = String.Format("[{0},{1},{2}] ", positionV.X, positionV.Y, positionV.Z);
				System.Diagnostics.Debug.Write (msg);
				#endif

				index++;
				x += DX;

				if (index % EvalsX == 0)
				{
					y+= DY;
					x = xMin;
				}
			} //end while loop

			#if MyDebug 
			System.Diagnostics.Debug.WriteLine("----------"); 
			#endif
		} //end of procedure

	#region "Old notes"

		// var / times hit
		//-------------
		//  n = (ra+rb+rc+rd+re+1)
		//  and ra == rb + rc + rd
		//  so by subst. of ra->n 
		//  n = 2*(rb+rc+rd) + 1
		//  P(rd + re) = 1
		//	P(rb + rc) = 1
		//  P(ra) = 1
		// && also 
		//
		//  ra = (rb + rc + rd)
		//  
		//   U = 1.0
		// P(E) = C(E)/C(U)
		// 

		#endregion
		/// <summary>
		/// Computes mTerrainVertices and mTerrainIndices
		/// </summary>
		/// 
		private void CreateTerrainData()
		{
			//we will not recreate the array we will use every data point because
			//there is at least one point in the data set that is used at least once
			if (EvalsX * EvalsY <= 0) throw new Exception("Invalid num of vertices. Cannot create terrain mesh-data.");

			short numTerrainIndices = (short)(3 * (DataCount));
			mTerrainIndices = new short[numTerrainIndices];

			short idxIdx = 0;
			for (short row = 0; row < EvalsY; row++)  //row and col are row and column index of the vertex in question
			{
				for (short col = 0; col < EvalsX; col++) //dont go to the last row or column we need our index to work in i - 1 notation.
				{
					//CustomVertex.PositionNormalColored p0 = mDataVertices[i]; //data vertex in question does not need to be instantiated. 
					//SEE DATA POINT we are dealing with. 

					if (idxIdx >= mTerrainIndices.Length)
						break;

					#if MyDebug
					System.Diagnostics.Debug.WriteLine(string.Format("idx = {0}, [r{1},c{2}]", idxIdx, row, col));
					#endif
					if (row == 0 && col == 0)
					{
						SetTerrainIndices(row, col, TriangleType.BELOW_RIGHT);
					}
					else if (row == 0 && (col > 0 && col < XDivisions) )
					{
						SetTerrainIndices(row, col, TriangleType.BELOW_RIGHT);
					}
					else if (row == 0 && (col == XDivisions ) )
					{
						SetTerrainIndices(row, col, TriangleType.BELOW_LEFT);
					}
					else if (row > 0)
					{
						if (row < YDivisions)
						{
							if (col >= 0 && col < XDivisions)
							{
								SetTerrainIndices(row, col, TriangleType.BELOW_RIGHT);
							}
							else if (col == XDivisions)
							{
								SetTerrainIndices(row, col, TriangleType.BELOW_LEFT);
							}
							else
								throw new Exception("Error A323 - unaccounted for value");
						}
						else if (row == YDivisions)
						{
							
							if (col >= 0 && col <= XDivisions)
							{
								SetTerrainIndices(row, col, TriangleType.ABOVE_RIGHT);
							}
							else if (col == XDivisions)
							{
								SetTerrainIndices(row, col, TriangleType.ABOVE_LEFT);
							}
							else
								throw new Exception("Error A324 - unaccounted for value");
						}
					}
					else
					{
						throw new Exception("Error A325 - unaccounted for value. Create Terrain Mesh Data: Col and row out of bounds");
					}

					#if MyDebug
					Debug_ShowTerrainTriangleAt(idxIdx);
					#endif

					idxIdx++; //put last, after data point has been totally "consumed".
				}//end for col				
			} //end for row

			#if MyDebug
			Debug.Assert(idxIdx == EvalsX*EvalsY);
			System.Diagnostics.Debug.Flush();
			#endif
		}

	#region "Unsafe way to get things done; old code"
		/*
	unsafe void DoForDx()
	{		int VertexCount = mDataVertices.Length;
			int FaceCount = DataCount;
			
			//int[] idata = new int [3];
			
			void* myNeedVoidDataReference = &mDataVertices;
			IntPtr myPointerToDataForGraphicsStream = new IntPtr(myNeedVoidDataReference);
			Microsoft.DirectX.GraphicsStream gs = new GraphicsStream
			(
				myPointerToDataForGraphicsStream, 
				mDataVertices.Length * (sizeof(CustomVertex.PositionNormalColored)),
				mRead,
				mWrite
			); 
		 */
#endregion "Unsafe way to get things done; old code"

		int VertexCount
		{			
			get {return mDataVertices.Length; }
		}

		int FaceCount
		{
			get { return EvalsX * EvalsY; }
		}

		private short[] GetTriangle(short row, short col)
		{
			//int row, int col
			int indexNum = DataIndexFromRC(row, col); //get the index
			return GetTriangle(indexNum);
		}

		private short[] GetTriangle(int indexStart)
		{
			short[] links = new short[3];
			links[0] = mTerrainIndices[indexStart];
			links[1] = mTerrainIndices[indexStart + 1];
			links[2] = mTerrainIndices[indexStart + 2];
			return links;
		}
	#endregion "Create data / compute data/ calculations "

		public enum TriangleType
		{
			ABOVE_LEFT,
			ABOVE_RIGHT,
			BELOW_LEFT,
			BELOW_RIGHT
		}
		//offset for v2 and v3:
		// v2yo v2xo  v3yo  v3x0 (row comes first, then column)
		short[] ABOVE_LEFT = { 0, -1, -1, -1 };
		short[] BELOW_RIGHT = { 1, 0, 1, 1 }; //seems to work good was originally what i did at the origin.
		short[] ABOVE_RIGHT = { -1, 0, -1, 1 };
		short[] BELOW_LEFT = { 1, 0, 1, -1 };


		private void Debug_ShowTerrainTriangleAt(short customVertexIndex)
		{
			short terrainIndex = (short) (customVertexIndex * 3);

			string msg = string.Format("[{0} {1} {2}] ",
				mTerrainIndices[terrainIndex],
				mTerrainIndices[terrainIndex + 1],
				mTerrainIndices[terrainIndex + 2]);

			System.Diagnostics.Debug.WriteLine(msg);
			System.Diagnostics.Debug.Flush();
		}

		private void SetTerrainIndices
		(short v1RowIndex, short v1ColIndex,
			TriangleType type
		)
		{
			//get 2 POINT offsets from triangle type.
			short v2RowOffset = 0;
			short v2ColOffset = 0;
			short v3RowOffset = 0;
			short v3ColOffset = 0;

			switch (type)
			{
				case TriangleType.ABOVE_LEFT:
					v2RowOffset = ABOVE_LEFT[0];
					v2ColOffset = ABOVE_LEFT[1];
					v3RowOffset = ABOVE_LEFT[2];
					v3ColOffset = ABOVE_LEFT[3];
					break;
				case TriangleType.ABOVE_RIGHT:
					v2RowOffset = ABOVE_RIGHT[0];
					v2ColOffset = ABOVE_RIGHT[1];
					v3RowOffset = ABOVE_RIGHT[2];
					v3ColOffset = ABOVE_RIGHT[3];
					break;
				case TriangleType.BELOW_LEFT:
					v2RowOffset = BELOW_LEFT[0];
					v2ColOffset = BELOW_LEFT[1];
					v3RowOffset = BELOW_LEFT[2];
					v3ColOffset = BELOW_LEFT[3];
					break;
				case TriangleType.BELOW_RIGHT:
					v2RowOffset = BELOW_RIGHT[0];
					v2ColOffset = BELOW_RIGHT[1];
					v3RowOffset = BELOW_RIGHT[2];
					v3ColOffset = BELOW_RIGHT[3];
					break;
			}
			short theIndex = DataIndexFromRC(v1RowIndex, v1ColIndex);
			short r2 = (short) (v1RowIndex + v2RowOffset);
			short c2 = (short)(v1ColIndex + v2ColOffset);
			short aidx2 = DataIndexFromRC(r2, c2);
			short r3 = (short) (v1RowIndex + v3RowOffset);
			short c3 = (short) (v1ColIndex + v3ColOffset);
			short aidx3 = DataIndexFromRC(r3, c3);

			short len = (short) (mTerrainIndices.Length); //do not make an index past this number

			//now check bounds
			if (aidx2 < len && aidx3 < len)
			{
				System.Diagnostics.Debug.Assert(theIndex < len);
				//do first side of square	      //0 * 3 = 0, 1*3 = 3, 2*3 =6
				//0     1     2     3       4
				int terrainOffset = theIndex * 3; //0 1 2 3 4 5 6 7 8 9 10 11 12
				mTerrainIndices[terrainOffset] = theIndex;
				mTerrainIndices[terrainOffset + 1] = aidx2;
				mTerrainIndices[terrainOffset + 2] = aidx3;
			}
		}

		private void DebugShowDataIndexMap()
		{
			System.Diagnostics.Debug.Write(">> ");

			for (short col = 0; col <= XDivisions; col++)
				System.Diagnostics.Debug.Write(col.ToString() + " ");

			System.Diagnostics.Debug.WriteLine("");
			System.Diagnostics.Debug.WriteLine(">>>>>>>>");

			for (short j = 0; j <= YDivisions; j++)
			{
				System.Diagnostics.Debug.Write(j.ToString() + "> ");
				for (short i = 0; i <= XDivisions; i++)
				{
					short index = DataIndexFromRC(j, i);
					System.Diagnostics.Debug.Write(index.ToString() + " ");
				}
				System.Diagnostics.Debug.WriteLine("");
			}
			System.Diagnostics.Debug.Flush();
		}
		/// <summary>
		///  Use row and column indices for ease of computation to an index in the array.
		///   In other words there exists a function which maps one-to-one from (row,col) onto (index).
		/// </summary>
		/// <param name="row"> row >= 0</param>
		/// <param name="col"> col >= 0</param>
		private short DataIndexFromRC(short row, short col)
		{
			//if (row < 0 || col < 0)
				//throw new Exception("Row or column index exception.");
			short index = (short) (row * EvalsX + col);
			return index;
		}

	#region "Functions that can be used to redefine the dataset"

		/// <summary>
		/// Color map
		/// </summary>
		/// <param name="point3d">A normalized vector from which map a color</param>
		/// <returns>A color in the form of int FromArgb</returns>
		private int ColorAt(Vector3 point3d)
		{
			//input a normalzied vector
			Color c = Color.FromArgb((int)point3d.X * 255, (int)point3d.Y * 255, (int)point3d.Z * 255);
			return c.ToArgb();
		}

		private static float F_z(float x, float y)
		{
			float z = (float)1;// (Math.Sin(x) * Math.Sin(y));
			return z;
		}

	#endregion "Functions that can be used to redefine the dataset"

	} //end of class!
}	  //end of namespace!!
